Django REST Framework

Von Models zur professionellen API

Vollständiger Leitfaden von der Geschichte bis zur Implementierung

Theorie • Praxis • Best Practices

📋 Was lernen wir heute?

1

Geschichte & Kontext

DRF Entwicklungsgeschichte

Warum wurde es eingeführt?

2

Theorie-Grundlagen

Serialisierung verstehen

CRUD-Operationen

URL-Design

3

Erweiterbarkeit

Zusatz-Packages

Filter & Pagination

Authentication

4

Praxis-Implementierung

Schritt-für-Schritt

Movie-API erstellen

Testen & Debuggen

📜 Die Geschichte von Django REST Framework

2011

🌱 Die Geburt

Tom Christie startet DRF als Open-Source-Projekt

  • Ursprünglich: "Django REST Framework"
  • Ziel: REST APIs in Django vereinfachen
  • Inspiration: Tastypie, Piston (frühere API-Frameworks)
2012

🚀 Version 2.0

Durchbruch mit revolutionären Features

  • Browsable API (interaktive Web-UI)
  • Serializer-Klassen (ähnlich Django Forms)
  • ViewSets & Routers
2014

💼 Mozilla Funding

Mozilla unterstützt DRF finanziell

  • Professionelle Entwicklung
  • Bessere Dokumentation
  • Stabilität für Enterprise
2015-2020

📈 Industriestandard

DRF wird zum De-facto-Standard

  • Version 3.x: Stabile API
  • Genutzt von: Instagram, Mozilla, Red Hat, Eventbrite
  • 100.000+ Projekte weltweit
2024

🎯 Heute

Version 3.14+ - Reif & Feature-reich

  • Async Support (Django 4.x+)
  • Schema-Generation (OpenAPI/Swagger)
  • Über 20.000 GitHub Stars

🤔 Warum wurde DRF eingeführt?

Die Probleme vor DRF (2010)

❌ Das Problem

REST APIs in Django (2010):

def movie_list(request):
    if request.method == 'GET':
        movies = Movie.objects.all()
        data = []
        for movie in movies:
            data.append({
                'id': movie.id,
                'title': movie.title,
                'year': movie.year,
                # ... 20 Felder manuell ...
            })
        return JsonResponse(data, safe=False)
    
    elif request.method == 'POST':
        data = json.loads(request.body)
        # Validierung manuell
        if not data.get('title'):
            return JsonResponse({'error': 'Title required'}, status=400)
        # ... 50 Zeilen Validierung ...
        
        try:
            movie = Movie.objects.create(...)
        except Exception as e:
            # ... Fehlerbehandlung ...
        
        return JsonResponse({...}, status=201)

# Das gleiche für PUT, PATCH, DELETE... 😫
  • 🔴 Viel Boilerplate-Code
  • 🔴 Keine Standardisierung
  • 🔴 Fehleranfällig
  • 🔴 Schwer zu testen
  • 🔴 Keine Dokumentation

✅ Die Lösung: DRF

Mit Django REST Framework:

class MovieSerializer(serializers.ModelSerializer):
    class Meta:
        model = Movie
        fields = '__all__'

class MovieViewSet(viewsets.ModelViewSet):
    queryset = Movie.objects.all()
    serializer_class = MovieSerializer

# Fertig! 🎉
# GET, POST, PUT, PATCH, DELETE funktionieren!
  • ✅ 90% weniger Code
  • ✅ Standards & Best Practices
  • ✅ Eingebaute Validierung
  • ✅ Automatische Tests
  • ✅ Browsable API (Doku gratis!)

💡 Das Ziel von DRF:

"Make building Web APIs as simple as possible"

- Tom Christie, Creator of DRF

🔄 Serialisierung - Theorie

Was bedeutet "Serialisierung"?

Die Umwandlung von komplexen Datenstrukturen in ein einfaches Format (und zurück)

📥 Deserialisierung (Input)

JSON → Python-Objekt

# Client sendet JSON:
{
  "title": "Inception",
  "year": 2010,
  "rating": "8.8"
}

# ↓ Deserialisierung ↓

# Django erstellt:
movie = Movie(
    title="Inception",
    year=2010,
    rating=Decimal('8.8')
)
movie.save()

📤 Serialisierung (Output)

Python-Objekt → JSON

# Django hat:
movie = Movie.objects.get(pk=1)
# 

# ↓ Serialisierung ↓

# Client erhält JSON:
{
  "id": 1,
  "title": "Inception",
  "year": 2010,
  "rating": "8.8",
  "created_at": "2024-01-15T10:00:00Z"
}

🎯 Warum brauchen wir Serialisierung?

  • Datenübertragung: JSON ist universell (Browser, Mobile Apps, andere Server)
  • Typ-Konvertierung: Python Decimal → JSON String
  • Validierung: Prüfen, ob Daten korrekt sind
  • Sicherheit: Nur erlaubte Felder zeigen

📦 Serialisierungsverfahren im Überblick

1. JSON (JavaScript Object Notation)

Der Standard für Web-APIs

{
  "title": "Inception",
  "year": 2010,
  "rating": 8.8
}
  • ✅ Menschenlesbar
  • ✅ Universell unterstützt
  • ✅ Leichtgewichtig
  • ❌ Keine Binärdaten direkt

2. XML (Extensible Markup Language)

Legacy-Standard

<movie>
  <title>Inception</title>
  <year>2010</year>
  <rating>8.8</rating>
</movie>
  • ✅ Strukturiert & validierbar
  • ❌ Verbose (viel Overhead)
  • ❌ Weniger performant

3. MessagePack

Binäres Format (kompakt)

# Binär codiert (kleiner als JSON)
# Gleiche Daten wie JSON, aber:
# JSON: 54 bytes
# MessagePack: 33 bytes
  • ✅ Sehr schnell
  • ✅ Kompakt
  • ❌ Nicht menschenlesbar

4. Protocol Buffers (Protobuf)

Google's Binärformat

// .proto Datei
message Movie {
  string title = 1;
  int32 year = 2;
  float rating = 3;
}
  • ✅ Extrem performant
  • ✅ Typsicher
  • ❌ Komplexer Setup

5. YAML

Human-friendly Format

title: Inception
year: 2010
rating: 8.8
  • ✅ Sehr lesbar
  • ✅ Gut für Konfiguration
  • ❌ Langsamer als JSON

6. BSON (Binary JSON)

MongoDB's Format

// Binär, aber JSON-ähnlich
// Unterstützt mehr Datentypen:
{
  "date": ISODate("2024-01-15"),
  "binary": BinData(...)
}
  • ✅ Mehr Datentypen
  • ✅ Schneller Parsing
  • ❌ Größer als JSON

🎯 DRF unterstützt standardmäßig:

  • JSON (default) - rest_framework.renderers.JSONRenderer
  • Browsable API (HTML) - rest_framework.renderers.BrowsableAPIRenderer
  • Weitere via Packages: XML, YAML, MessagePack, CSV

🔧 CRUD-Operationen - Theorie

CRUD = Die vier Grundoperationen

Create, Retrieve, Update, Delete - Basis jeder Datenbank-Anwendung

📝 CREATE (Erstellen)

HTTP: POST

POST /api/movies/
Content-Type: application/json

{
  "title": "Inception",
  "year": 2010
}

→ 201 Created
{
  "id": 1,
  "title": "Inception",
  "year": 2010
}
  • Neue Ressource anlegen
  • Status: 201 Created
  • Response: Erstelltes Objekt

👁️ RETRIEVE (Abrufen)

HTTP: GET

# Liste aller Ressourcen
GET /api/movies/
→ 200 OK
[
  {"id": 1, "title": "Inception"},
  {"id": 2, "title": "Matrix"}
]

# Eine spezifische Ressource
GET /api/movies/1/
→ 200 OK
{"id": 1, "title": "Inception", ...}
  • Daten abrufen (keine Änderung)
  • Status: 200 OK
  • Kann gecacht werden

✏️ UPDATE (Aktualisieren)

HTTP: PUT / PATCH

# PUT: Komplette Aktualisierung
PUT /api/movies/1/
{
  "title": "Inception",
  "year": 2010,
  "rating": 9.0
}
→ 200 OK

# PATCH: Teilweise Aktualisierung
PATCH /api/movies/1/
{
  "rating": 9.0
}
→ 200 OK
  • PUT: Alle Felder
  • PATCH: Nur geänderte Felder
  • Status: 200 OK

🗑️ DELETE (Löschen)

HTTP: DELETE

DELETE /api/movies/1/

→ 204 No Content

# Ressource ist gelöscht
GET /api/movies/1/
→ 404 Not Found
  • Ressource entfernen
  • Status: 204 No Content
  • Keine Response-Body nötig

🎯 In DRF mit ModelViewSet:

Alle 5 CRUD-Operationen automatisch: list, create, retrieve, update, partial_update, destroy

🔗 URL-Design - REST Best Practices

Gutes URL-Design ist entscheidend!

URLs sollten intuitiv, konsistent und ressourcen-orientiert sein

❌ Schlechtes URL-Design

# Verben in URLs (falsch!)
GET /api/getAllMovies
GET /api/getMovie?id=1
POST /api/createMovie
POST /api/updateMovie?id=1
POST /api/deleteMovie?id=1

# Probleme:
- Inkonsistent
- Nicht REST-konform
- Schwer zu merken
- Skaliert nicht

✅ Gutes URL-Design (REST)

# Ressourcen-orientiert
GET    /api/movies/           # Liste
POST   /api/movies/           # Erstellen
GET    /api/movies/1/         # Details
PUT    /api/movies/1/         # Aktualisieren
PATCH  /api/movies/1/         # Teil-Update
DELETE /api/movies/1/         # Löschen

# Vorteile:
✅ HTTP-Methoden nutzen Semantik
✅ Konsistent
✅ Intuitiv
✅ Standard-konform

📐 REST URL-Prinzipien

  • 1. Nomen statt Verben: /movies nicht /getMovies
  • 2. Plural für Collections: /movies/ nicht /movie/
  • 3. IDs in URL: /movies/1/ nicht /movies?id=1
  • 4. Verschachtelung für Beziehungen: /movies/1/castings/
  • 5. Kleinbuchstaben & Bindestriche: /movie-genres/ nicht /MovieGenres/

🎨 URL-Strukturen in DRF

1. Flache Struktur (Standard):

/api/movies/
/api/artists/
/api/castings/

2. Verschachtelte Ressourcen:

/api/movies/1/castings/       # Besetzung eines Films
/api/artists/5/movies/        # Filme eines Künstlers

3. Custom Actions:

/api/movies/top-rated/        # Custom Endpoint
/api/movies/recent/           # Custom Filter
/api/artists/1/filmography/   # Detailansicht

4. Versionierung:

/api/v1/movies/               # Version 1
/api/v2/movies/               # Version 2

🗺️ URL-Muster in der Praxis

Beispiel: Movie-API URL-Struktur

# STANDARD CRUD
GET    /api/movies/                    # Alle Filme
POST   /api/movies/                    # Film erstellen
GET    /api/movies/{id}/               # Film-Details
PUT    /api/movies/{id}/               # Film komplett aktualisieren
PATCH  /api/movies/{id}/               # Film teilweise aktualisieren
DELETE /api/movies/{id}/               # Film löschen

# FILTERING & SEARCH
GET    /api/movies/?year=2010          # Filter nach Jahr
GET    /api/movies/?genre=Sci-Fi       # Filter nach Genre
GET    /api/movies/?search=Matrix      # Suche im Titel
GET    /api/movies/?ordering=-rating   # Sortierung nach Rating

# PAGINATION
GET    /api/movies/?page=2             # Seite 2
GET    /api/movies/?page=2&page_size=5 # 5 Einträge pro Seite

# RELATIONSHIPS
GET    /api/movies/{id}/castings/      # Besetzung eines Films
GET    /api/artists/{id}/movies/       # Filme eines Künstlers

# CUSTOM ACTIONS
GET    /api/movies/top-rated/          # Top 10 Filme
GET    /api/movies/recent/             # Aktuelle Filme
GET    /api/movies/{id}/similar/       # Ähnliche Filme

# BATCH OPERATIONS (erweitert)
POST   /api/movies/bulk-create/        # Mehrere Filme erstellen
PATCH  /api/movies/bulk-update/        # Mehrere Filme updaten
DELETE /api/movies/bulk-delete/        # Mehrere Filme löschen

🎯 DRF Router generiert automatisch:

Mit DefaultRouter werden Standard-URLs automatisch erstellt!

📦 Zusatz-Packages für DRF

DRF Ökosystem - Erweitere deine API!

🔍 1. django-filter

Fortgeschrittenes Filtering

pip install django-filter

# Ermöglicht:
GET /api/movies/?year__gte=2010
GET /api/movies/?rating__range=8,10
GET /api/movies/?genre__icontains=sci
  • Komplexe Filter-Queries
  • Range, Greater/Less than
  • Case-insensitive Suche

📄 2. drf-spectacular

OpenAPI/Swagger Dokumentation

pip install drf-spectacular

# Generiert automatisch:
- Swagger UI
- OpenAPI 3.0 Schema
- Interaktive API-Doku
  • Automatische API-Dokumentation
  • Try-it-out Funktionalität
  • Client-Code-Generierung

🔐 3. djangorestframework-simplejwt

JWT Authentication

pip install djangorestframework-simplejwt

# JSON Web Tokens für Auth:
POST /api/token/
{
  "username": "user",
  "password": "pass"
}
→ {"access": "...", "refresh": "..."}
  • Stateless Authentication
  • Access & Refresh Tokens
  • Sicher & skalierbar

🌐 4. django-cors-headers

CORS-Support

pip install django-cors-headers

# Erlaubt API-Zugriff von anderen Domains:
- Frontend auf localhost:3000
- Mobile Apps
- Third-Party Services
  • Cross-Origin Requests
  • Wichtig für SPAs
  • Konfigurierbare Policies

⚡ 5. django-rest-framework-gis

Geo-Daten Support

pip install djangorestframework-gis

# Für Location-basierte APIs:
- GPS-Koordinaten
- Geo-Queries (Umkreissuche)
- GeoJSON Format

📊 6. drf-excel

Excel Export

pip install drf-excel

# Export als Excel:
GET /api/movies/?format=xlsx
→ Excel-Datei Download

🔄 7. drf-nested-routers

Verschachtelte URLs

pip install drf-nested-routers

# Ermöglicht:
/api/movies/1/castings/
/api/movies/1/castings/5/

🎨 8. drf-yasg

Alternative API-Doku

pip install drf-yasg

# Swagger & ReDoc UI
# Ältere Alternative zu drf-spectacular

🔒 9. drf-access-policy

Granulare Permissions

pip install drf-access-policy

# Policy-basierte Zugriffskontrolle
# Besser als Django's Standard-Permissions

📈 10. django-silk

Profiling & Monitoring

pip install django-silk

# Performance-Analyse:
- SQL-Query-Tracking
- Request/Response-Profiling
- Bottleneck-Erkennung

📁 Projektstruktur - Vorbereitung

Bevor wir starten: Unser Projekt

Verzeichnisstruktur:

FirstMovieAPI/
├── firstmovieapi/          # Projekt-Settings
│   ├── __init__.py
│   ├── settings.py         # ← Werden wir anpassen
│   ├── urls.py             # ← Werden wir anpassen
│   └── wsgi.py
├── movies/                 # Unsere App
│   ├── __init__.py
│   ├── models.py           # ← Bereits vorhanden (Movie, Artist, MovieCasting)
│   ├── serializers.py      # ← NEU erstellen
│   ├── views.py            # ← NEU erstellen
│   ├── urls.py             # ← NEU erstellen
│   ├── admin.py
│   └── migrations/
├── manage.py
└── db.sqlite3

Unsere Models (bereits vorhanden):

class Movie(models.Model):
    title = models.CharField(max_length=200)
    year = models.IntegerField()
    genre = models.CharField(max_length=100, blank=True)
    rating = models.DecimalField(max_digits=3, decimal_places=1, null=True, blank=True)
    description = models.TextField(blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

class Artist(models.Model):
    first_name = models.CharField(max_length=100)
    last_name = models.CharField(max_length=100)
    birth_date = models.DateField(null=True, blank=True)
    nationality = models.CharField(max_length=100, blank=True)
    biography = models.TextField(blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

class MovieCasting(models.Model):
    movie = models.ForeignKey(Movie, on_delete=models.CASCADE, related_name='castings')
    artist = models.ForeignKey(Artist, on_delete=models.CASCADE, related_name='movie_roles')
    role_name = models.CharField(max_length=200)
    is_main_role = models.BooleanField(default=False)
    order = models.IntegerField(default=0)
    created_at = models.DateTimeField(auto_now_add=True)

📦 Schritt 1: DRF & Packages installieren

1.1

🔧 Django REST Framework

# Terminal / PowerShell:
pip install djangorestframework

# Output:
Collecting djangorestframework
Successfully installed djangorestframework-3.14.0
1.2

🔍 django-filter (für Filtering)

pip install django-filter

# Output:
Collecting django-filter
Successfully installed django-filter-23.5
1.3

🌐 django-cors-headers (optional, für Frontend)

pip install django-cors-headers

# Nur nötig wenn Frontend von anderer Domain zugreift
1.4

📄 drf-spectacular (optional, für Swagger-Doku)

pip install drf-spectacular

# Generiert automatisch Swagger/OpenAPI Dokumentation

✅ Installierte Packages:

  • djangorestframework: Kern-Framework (PFLICHT)
  • django-filter: Für Filtering (EMPFOHLEN)
  • django-cors-headers: Für Cross-Origin (Optional)
  • drf-spectacular: Für API-Doku (Optional)

⚙️ Schritt 2: Settings konfigurieren

firstmovieapi/settings.py anpassen:

# filepath: firstmovieapi/settings.py
# ...existing code...

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    
    # Third-party apps
    'rest_framework',        # ← NEU! Django REST Framework
    'django_filters',        # ← NEU! Filtering-Support
    'corsheaders',           # ← NEU! (optional) CORS-Support
    'drf_spectacular',       # ← NEU! (optional) API-Dokumentation
    
    # Local apps
    'movies',
]

# ...existing code...

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'corsheaders.middleware.CorsMiddleware',  # ← NEU! (optional) CORS
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

# ...existing code...

# ==============================================
# REST FRAMEWORK KONFIGURATION (am Ende der Datei)
# ==============================================

REST_FRAMEWORK = {
    # Schema-Generator (für Swagger)
    'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',  # optional
    
    # Permissions (Zugriffsrechte)
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.AllowAny',  # Für Development: Jeder hat Zugriff
        # In Produktion: 'rest_framework.permissions.IsAuthenticated',
    ],
    
    # Pagination (Seitennummerierung)
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 10,  # 10 Einträge pro Seite
    
    # Renderer (Output-Formate)
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',          # JSON-Output
        'rest_framework.renderers.BrowsableAPIRenderer',  # Interaktive Web-UI
    ],
    
    # Filter Backends
    'DEFAULT_FILTER_BACKENDS': [
        'django_filters.rest_framework.DjangoFilterBackend',  # django-filter
        'rest_framework.filters.SearchFilter',                # Suche
        'rest_framework.filters.OrderingFilter',              # Sortierung
    ],
    
    # Throttling (Rate Limiting) - optional
    'DEFAULT_THROTTLE_CLASSES': [
        'rest_framework.throttling.AnonRateThrottle',   # Für anonyme User
        'rest_framework.throttling.UserRateThrottle',   # Für eingeloggte User
    ],
    'DEFAULT_THROTTLE_RATES': {
        'anon': '100/hour',   # 100 Requests pro Stunde für Anonyme
        'user': '1000/hour',  # 1000 Requests pro Stunde für User
    },
}

# ==============================================
# CORS HEADERS KONFIGURATION (optional)
# ==============================================

CORS_ALLOWED_ORIGINS = [
    "http://localhost:3000",    # React/Vue/Angular Frontend
    "http://localhost:8080",
    "http://127.0.0.1:3000",
]

# Oder für Development: ALLE Domains erlauben (NICHT in Produktion!)
# CORS_ALLOW_ALL_ORIGINS = True

# ==============================================
# DRF SPECTACULAR KONFIGURATION (optional)
# ==============================================

SPECTACULAR_SETTINGS = {
    'TITLE': 'Movie API',
    'DESCRIPTION': 'REST API für Filme, Künstler und Besetzungen',
    'VERSION': '1.0.0',
    'SERVE_INCLUDE_SCHEMA': False,
}

🔍 Was haben wir konfiguriert?

  • INSTALLED_APPS: DRF & Packages registriert
  • REST_FRAMEWORK: Permissions, Pagination, Renderer, Filter
  • CORS: Frontend-Zugriff erlauben (optional)
  • SPECTACULAR: API-Dokumentation (optional)

🔄 Schritt 3: Serializers erstellen

Serializers = Das Herzstück der API

Konvertieren Models automatisch zu JSON (und zurück)

Erstelle: movies/serializers.py

# filepath: movies/serializers.py
from rest_framework import serializers
from .models import Movie, Artist, MovieCasting


class MovieSerializer(serializers.ModelSerializer):
    """
    Serializer für Movie Model
    Konvertiert Movie-Objekte zu JSON und validiert Input
    """
    # Berechnetes Feld: Alter des Films
    age = serializers.SerializerMethodField()
    
    class Meta:
        model = Movie
        fields = '__all__'  # Alle Felder einbeziehen
        read_only_fields = ['created_at', 'updated_at']  # Nicht änderbar
    
    def get_age(self, obj):
        """Berechne das Alter des Films"""
        from datetime import datetime
        return datetime.now().year - obj.year
    
    def validate_year(self, value):
        """Validiere das Jahr"""
        from datetime import datetime
        current_year = datetime.now().year
        
        if value < 1888:  # Erstes Kino-Jahr
            raise serializers.ValidationError(
                "Das Jahr kann nicht vor 1888 liegen (Geburt des Kinos)"
            )
        
        if value > current_year + 5:  # Max 5 Jahre in Zukunft
            raise serializers.ValidationError(
                f"Das Jahr kann nicht mehr als 5 Jahre in der Zukunft liegen"
            )
        
        return value
    
    def validate_rating(self, value):
        """Validiere die Bewertung"""
        if value is not None and (value < 0 or value > 10):
            raise serializers.ValidationError(
                "Die Bewertung muss zwischen 0 und 10 liegen"
            )
        return value


class ArtistSerializer(serializers.ModelSerializer):
    """
    Serializer für Artist Model
    """
    # Property aus Model als Read-Only Feld
    full_name = serializers.ReadOnlyField()
    
    # Berechnetes Feld: Anzahl Filme
    movie_count = serializers.SerializerMethodField()
    
    class Meta:
        model = Artist
        fields = '__all__'
        read_only_fields = ['created_at', 'updated_at']
    
    def get_movie_count(self, obj):
        """Anzahl der Filme, in denen der Künstler mitspielt"""
        return obj.movie_roles.count()


class MovieCastingSerializer(serializers.ModelSerializer):
    """
    Einfacher Casting Serializer (nur IDs)
    Für CREATE/UPDATE Operationen
    """
    class Meta:
        model = MovieCasting
        fields = '__all__'
        read_only_fields = ['created_at']


class MovieCastingDetailSerializer(serializers.ModelSerializer):
    """
    Detaillierter Casting Serializer mit verschachtelten Objekten
    Für LIST/RETRIEVE Operationen
    """
    # Verschachtelte Serializer (für Output)
    movie = MovieSerializer(read_only=True)
    artist = ArtistSerializer(read_only=True)
    
    # IDs für Input (Write-Only)
    movie_id = serializers.PrimaryKeyRelatedField(
        queryset=Movie.objects.all(),
        source='movie',
        write_only=True
    )
    artist_id = serializers.PrimaryKeyRelatedField(
        queryset=Artist.objects.all(),
        source='artist',
        write_only=True
    )
    
    class Meta:
        model = MovieCasting
        fields = '__all__'
        read_only_fields = ['created_at']

✨ Serializer Features im Detail

🎯 ModelSerializer

class MovieSerializer(serializers.ModelSerializer):
    class Meta:
        model = Movie
        fields = '__all__'
  • Automatische Feld-Erkennung aus Model
  • fields = '__all__' - Alle Felder
  • fields = ['id', 'title'] - Nur bestimmte
  • exclude = ['password'] - Bestimmte ausschließen

📖 Read-Only Fields

read_only_fields = ['created_at', 'updated_at']
  • Felder nur zum Lesen
  • Werden bei POST/PUT ignoriert
  • Automatisch gesetzt (timestamps)

✏️ Write-Only Fields

password = serializers.CharField(
    write_only=True
)
  • Felder nur zum Schreiben
  • Werden nicht in Response angezeigt
  • Gut für Passwörter, Tokens

🧮 SerializerMethodField

age = serializers.SerializerMethodField()

def get_age(self, obj):
    return datetime.now().year - obj.year
  • Berechnete Felder
  • Methode: get_<feldname>
  • Nur Read-Only

✅ Feld-Validierung

def validate_year(self, value):
    if value < 1888:
        raise serializers.ValidationError(
            "Jahr zu früh"
        )
    return value
  • Methode: validate_<feldname>
  • Automatisch aufgerufen
  • Fehler mit ValidationError

🔗 Verschachtelte Serializer

movie = MovieSerializer(read_only=True)
artist = ArtistSerializer(read_only=True)
  • Komplette Objekte statt IDs
  • Praktisch für Detail-Ansichten
  • read_only=True empfohlen

🎯 Schritt 4: ViewSets erstellen

ViewSets = Eine Klasse für alle CRUD-Operationen

Automatische Implementierung von list, create, retrieve, update, destroy

Erstelle: movies/views.py

# filepath: movies/views.py
from rest_framework import viewsets, filters, status
from rest_framework.decorators import action
from rest_framework.response import Response
from django_filters.rest_framework import DjangoFilterBackend
from .models import Movie, Artist, MovieCasting
from .serializers import (
    MovieSerializer,
    ArtistSerializer,
    MovieCastingSerializer,
    MovieCastingDetailSerializer
)


class MovieViewSet(viewsets.ModelViewSet):
    """
    ViewSet für Movie Model
    
    Bietet automatisch folgende Endpoints:
    - list (GET /api/movies/) - Alle Filme
    - create (POST /api/movies/) - Film erstellen
    - retrieve (GET /api/movies/{id}/) - Film-Details
    - update (PUT /api/movies/{id}/) - Film komplett aktualisieren
    - partial_update (PATCH /api/movies/{id}/) - Film teilweise aktualisieren
    - destroy (DELETE /api/movies/{id}/) - Film löschen
    """
    queryset = Movie.objects.all()
    serializer_class = MovieSerializer
    
    # Filter Backends
    filter_backends = [
        DjangoFilterBackend,  # Für exakte Filter
        filters.SearchFilter,  # Für Textsuche
        filters.OrderingFilter  # Für Sortierung
    ]
    
    # Welche Felder können gefiltert werden?
    filterset_fields = ['year', 'genre']  # ?year=2010&genre=Sci-Fi
    
    # Welche Felder können durchsucht werden?
    search_fields = ['title', 'description']  # ?search=Matrix
    
    # Welche Felder können sortiert werden?
    ordering_fields = ['year', 'rating', 'title']  # ?ordering=-rating
    
    # Standard-Sortierung
    ordering = ['-year', 'title']  # Neueste zuerst, dann alphabetisch
    
    @action(detail=True, methods=['get'])
    def castings(self, request, pk=None):
        """
        Custom Endpoint: GET /api/movies/{id}/castings/
        Gibt alle Besetzungen eines Films zurück
        """
        movie = self.get_object()
        castings = movie.castings.all()
        serializer = MovieCastingDetailSerializer(castings, many=True)
        return Response(serializer.data)
    
    @action(detail=False, methods=['get'])
    def top_rated(self, request):
        """
        Custom Endpoint: GET /api/movies/top_rated/
        Gibt die Top 10 bestbewerteten Filme zurück
        """
        movies = Movie.objects.filter(
            rating__isnull=False
        ).order_by('-rating')[:10]
        
        serializer = self.get_serializer(movies, many=True)
        return Response(serializer.data)
    
    @action(detail=False, methods=['get'])
    def recent(self, request):
        """
        Custom Endpoint: GET /api/movies/recent/
        Gibt Filme der letzten 5 Jahre zurück
        """
        from datetime import datetime
        current_year = datetime.now().year
        movies = Movie.objects.filter(year__gte=current_year - 5)
        
        serializer = self.get_serializer(movies, many=True)
        return Response(serializer.data)
    
    @action(detail=True, methods=['post'])
    def add_to_favorites(self, request, pk=None):
        """
        Custom Endpoint: POST /api/movies/{id}/add_to_favorites/
        Beispiel für Custom POST Action
        """
        movie = self.get_object()
        # Hier könnte Logik für Favoriten sein
        return Response({
            'status': 'Film zu Favoriten hinzugefügt',
            'movie': movie.title
        })


class ArtistViewSet(viewsets.ModelViewSet):
    """
    ViewSet für Artist Model
    """
    queryset = Artist.objects.all()
    serializer_class = ArtistSerializer
    
    filter_backends = [
        filters.SearchFilter,
        filters.OrderingFilter
    ]
    
    search_fields = ['first_name', 'last_name', 'nationality', 'biography']
    ordering_fields = ['last_name', 'first_name', 'birth_date']
    ordering = ['last_name', 'first_name']
    
    @action(detail=True, methods=['get'])
    def movies(self, request, pk=None):
        """
        Custom Endpoint: GET /api/artists/{id}/movies/
        Gibt alle Filme eines Künstlers zurück
        """
        artist = self.get_object()
        castings = artist.movie_roles.all()
        movies = [casting.movie for casting in castings]
        
        serializer = MovieSerializer(movies, many=True)
        return Response(serializer.data)
    
    @action(detail=True, methods=['get'])
    def filmography(self, request, pk=None):
        """
        Custom Endpoint: GET /api/artists/{id}/filmography/
        Detaillierte Filmografie mit Rollen
        """
        artist = self.get_object()
        castings = artist.movie_roles.all().select_related('movie')
        
        # Gruppiere nach Hauptrolle/Nebenrolle
        main_roles = castings.filter(is_main_role=True)
        supporting_roles = castings.filter(is_main_role=False)
        
        return Response({
            'artist': ArtistSerializer(artist).data,
            'main_roles': MovieCastingDetailSerializer(main_roles, many=True).data,
            'supporting_roles': MovieCastingDetailSerializer(supporting_roles, many=True).data,
            'total_movies': castings.count()
        })


class MovieCastingViewSet(viewsets.ModelViewSet):
    """
    ViewSet für MovieCasting Model
    """
    queryset = MovieCasting.objects.all().select_related('movie', 'artist')
    
    filter_backends = [DjangoFilterBackend]
    filterset_fields = ['movie', 'artist', 'is_main_role']
    
    def get_serializer_class(self):
        """
        Wähle den passenden Serializer basierend auf der Action
        """
        if self.action in ['list', 'retrieve']:
            # Für Anzeige: Mit verschachtelten Objekten
            return MovieCastingDetailSerializer
        # Für Create/Update: Nur IDs
        return MovieCastingSerializer

🔧 ViewSet Actions im Detail

📋 Standard Actions (Automatisch)

class MovieViewSet(viewsets.ModelViewSet):
    queryset = Movie.objects.all()
    serializer_class = MovieSerializer

Generiert automatisch:

  • list() → GET /api/movies/
  • create() → POST /api/movies/
  • retrieve() → GET /api/movies/{id}/
  • update() → PUT /api/movies/{id}/
  • partial_update() → PATCH /api/movies/{id}/
  • destroy() → DELETE /api/movies/{id}/

🎨 Custom Actions (Detail)

@action(detail=True, methods=['get'])
def castings(self, request, pk=None):
    movie = self.get_object()
    # ...
    return Response(data)
  • detail=True → Braucht ID
  • URL: /api/movies/{id}/castings/
  • pk Parameter automatisch
  • self.get_object() holt das Objekt

📊 Custom Actions (Liste)

@action(detail=False, methods=['get'])
def top_rated(self, request):
    movies = Movie.objects.filter(...)
    # ...
    return Response(data)
  • detail=False → Keine ID nötig
  • URL: /api/movies/top_rated/
  • Arbeitet mit QuerySet
  • Für Listen/Filter-Operationen

✏️ Custom POST Actions

@action(detail=True, methods=['post'])
def add_to_favorites(self, request, pk=None):
    movie = self.get_object()
    # Verarbeite request.data
    return Response({'status': 'ok'})
  • methods=['post'] → POST-Request
  • Zugriff auf request.data
  • Für Aktionen (nicht CRUD)

🔗 Schritt 5: URLs & Router konfigurieren

Router = Automatische URL-Generierung

DRF Router erstellt alle URLs aus ViewSets

1. Erstelle: movies/urls.py

# filepath: movies/urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import MovieViewSet, ArtistViewSet, MovieCastingViewSet

# Router erstellen
router = DefaultRouter()

# ViewSets beim Router registrieren
router.register(r'movies', MovieViewSet, basename='movie')
router.register(r'artists', ArtistViewSet, basename='artist')
router.register(r'castings', MovieCastingViewSet, basename='casting')

# URLs
urlpatterns = [
    # Router-URLs einbinden
    path('', include(router.urls)),
]

2. Registriere in: firstmovieapi/urls.py

# filepath: firstmovieapi/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    # Django Admin
    path('admin/', admin.site.urls),
    
    # API Endpunkte
    path('api/', include('movies.urls')),
    
    # DRF Login/Logout für Browsable API
    path('api-auth/', include('rest_framework.urls')),
    
    # Optional: Swagger Dokumentation
    # path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
    # path('api/docs/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
]

🎉 Automatisch generierte URLs:

# MOVIES
GET    /api/movies/                    # Liste aller Filme
POST   /api/movies/                    # Film erstellen
GET    /api/movies/{id}/               # Film-Details
PUT    /api/movies/{id}/               # Film komplett aktualisieren
PATCH  /api/movies/{id}/               # Film teilweise aktualisieren
DELETE /api/movies/{id}/               # Film löschen
GET    /api/movies/{id}/castings/      # Custom: Besetzung
GET    /api/movies/top_rated/          # Custom: Top-Filme
GET    /api/movies/recent/             # Custom: Aktuelle Filme

# ARTISTS
GET    /api/artists/                   # Liste aller Künstler
POST   /api/artists/                   # Künstler erstellen
GET    /api/artists/{id}/              # Künstler-Details
PUT    /api/artists/{id}/              # Künstler aktualisieren
PATCH  /api/artists/{id}/              # Künstler teilweise aktualisieren
DELETE /api/artists/{id}/              # Künstler löschen
GET    /api/artists/{id}/movies/       # Custom: Filme des Künstlers
GET    /api/artists/{id}/filmography/  # Custom: Filmografie

# CASTINGS
GET    /api/castings/                  # Liste aller Besetzungen
POST   /api/castings/                  # Besetzung erstellen
GET    /api/castings/{id}/             # Besetzung-Details
PUT    /api/castings/{id}/             # Besetzung aktualisieren
PATCH  /api/castings/{id}/             # Besetzung teilweise aktualisieren
DELETE /api/castings/{id}/             # Besetzung löschen

🔍 Filtering - Theorie & Praxis

django-filter ermöglicht komplexe Queries

1️⃣ Einfache Filter

# In ViewSet definiert:
filterset_fields = ['year', 'genre']

# Verwendung:
GET /api/movies/?year=2010
GET /api/movies/?genre=Sci-Fi
GET /api/movies/?year=2010&genre=Action

2️⃣ Lookup-Filter

# Django ORM Lookups:
GET /api/movies/?year__gte=2010        # Jahr >= 2010
GET /api/movies/?year__lte=2020        # Jahr <= 2020
GET /api/movies/?year__range=2010,2020 # 2010-2020
GET /api/movies/?rating__gte=8.0       # Rating >= 8.0
GET /api/movies/?genre__icontains=sci  # Genre enthält "sci"

Verfügbare Lookups:

  • __exact - Genau gleich
  • __iexact - Case-insensitive gleich
  • __contains - Enthält
  • __icontains - Case-insensitive enthält
  • __gt, __gte - Greater than (or equal)
  • __lt, __lte - Less than (or equal)
  • __range - Zwischen zwei Werten
  • __isnull - Ist NULL

3️⃣ Search Filter

# In ViewSet definiert:
search_fields = ['title', 'description']

# Verwendung:
GET /api/movies/?search=Matrix
GET /api/movies/?search=Inception

# Sucht in title UND description
# Case-insensitive
# Partial Match (enthält)

4️⃣ Ordering Filter

# In ViewSet definiert:
ordering_fields = ['year', 'rating', 'title']
ordering = ['-year']  # Standard

# Verwendung:
GET /api/movies/?ordering=year          # Aufsteigend
GET /api/movies/?ordering=-year         # Absteigend
GET /api/movies/?ordering=-rating,title # Mehrfach

🎨 Custom FilterSet (Erweitert)

Für komplexere Filter: FilterSet-Klasse erstellen

# filepath: movies/filters.py
import django_filters
from .models import Movie


class MovieFilter(django_filters.FilterSet):
    """Custom Filter für Movie Model"""
    
    # Jahr-Range
    year_min = django_filters.NumberFilter(field_name='year', lookup_expr='gte')
    year_max = django_filters.NumberFilter(field_name='year', lookup_expr='lte')
    
    # Rating-Range
    rating_min = django_filters.NumberFilter(field_name='rating', lookup_expr='gte')
    rating_max = django_filters.NumberFilter(field_name='rating', lookup_expr='lte')
    
    # Genre mit Case-Insensitive
    genre = django_filters.CharFilter(lookup_expr='icontains')
    
    # Titel enthält
    title_contains = django_filters.CharFilter(field_name='title', lookup_expr='icontains')
    
    # Nur Filme mit Rating
    has_rating = django_filters.BooleanFilter(field_name='rating', lookup_expr='isnull', exclude=True)
    
    class Meta:
        model = Movie
        fields = ['genre']

In ViewSet verwenden:

# filepath: movies/views.py
from .filters import MovieFilter

class MovieViewSet(viewsets.ModelViewSet):
    queryset = Movie.objects.all()
    serializer_class = MovieSerializer
    filterset_class = MovieFilter  # ← Custom FilterSet
    # ...existing code...

Verwendung:

GET /api/movies/?year_min=2010&year_max=2020
GET /api/movies/?rating_min=8.0
GET /api/movies/?title_contains=matrix
GET /api/movies/?has_rating=true
GET /api/movies/?genre=sci&year_min=2010&rating_min=8.0

📄 Pagination - Automatische Seitennummerierung

Verhindert zu große Responses

DRF paginiert automatisch große Datenmengen

📥 Request

GET /api/movies/?page=2

📤 Response (Paginiert)

{
  "count": 145,
  "next": "http://api.example.org/movies/?page=3",
  "previous": "http://api.example.org/movies/?page=1",
  "results": [
    {"id": 11, "title": "Movie 11", ...},
    {"id": 12, "title": "Movie 12", ...},
    // ... 10 Einträge pro Seite
  ]
}

📐 Pagination-Typen in DRF

1. PageNumberPagination (Standard)

# In settings.py:
REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 
        'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 10
}

# Verwendung:
GET /api/movies/?page=1
GET /api/movies/?page=2
GET /api/movies/?page=3

2. LimitOffsetPagination

# Flexiblere Kontrolle:
GET /api/movies/?limit=20&offset=0    # Erste 20
GET /api/movies/?limit=20&offset=20   # Nächste 20

3. CursorPagination

# Für sehr große Datasets (Performance):
GET /api/movies/?cursor=cD0yMDIz...

🎨 Custom Page Size

# Client kann Page Size ändern:
GET /api/movies/?page=1&page_size=50

# Max Limit setzen in settings.py:
REST_FRAMEWORK = {
    'PAGE_SIZE': 10,
    'MAX_PAGE_SIZE': 100,  # Maximal 100
}

🧪 Schritt 6: API testen - Vorbereitung

1

🔄 Migrationen erstellen & ausführen

# Terminal:
python manage.py makemigrations
python manage.py migrate

# Output:
Migrations for 'movies':
  movies/migrations/0001_initial.py
    - Create model Movie
    - Create model Artist
    - Create model MovieCasting
Operations to perform:
  Apply all migrations: movies
Running migrations:
  Applying movies.0001_initial... OK
2

👤 Superuser erstellen (für Admin & Browsable API)

# Terminal:
python manage.py createsuperuser

# Eingaben:
Username: admin
Email: admin@example.com
Password: ********
Password (again): ********
Superuser created successfully.
3

🚀 Server starten

# Terminal:
python manage.py runserver

# Output:
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.

🌐 Browsable API - Der einfachste Weg

DRF's Killer-Feature: Interaktive Web-UI!

Teste deine API direkt im Browser - keine extra Tools nötig

1

🔗 API Root öffnen

http://127.0.0.1:8000/api/

# Du siehst:
- movies: http://127.0.0.1:8000/api/movies/
- artists: http://127.0.0.1:8000/api/artists/
- castings: http://127.0.0.1:8000/api/castings/

Die Browsable API zeigt:

  • Alle verfügbaren Endpunkte
  • Interaktive Formulare
  • JSON/HTML Toggle
  • Filter-Optionen
2

➕ Film erstellen (POST)

Gehe zu: http://127.0.0.1:8000/api/movies/

Scrolle nach unten zum Formular:

{
  "title": "The Matrix",
  "year": 1999,
  "genre": "Sci-Fi",
  "rating": "8.7",
  "description": "A computer hacker learns from mysterious rebels about the true nature of his reality."
}

Klicke auf "POST"

✅ Film wurde erstellt mit ID 1

3

📋 Filme anzeigen (GET)

Gleiche Seite neu laden → Siehst die Filmliste

[
  {
    "id": 1,
    "title": "The Matrix",
    "year": 1999,
    "genre": "Sci-Fi",
    "rating": "8.7",
    "description": "A computer hacker...",
    "age": 25,
    "created_at": "2024-11-16T10:00:00Z",
    "updated_at": "2024-11-16T10:00:00Z"
  }
]
4

✏️ Film aktualisieren (PUT/PATCH)

Gehe zu: http://127.0.0.1:8000/api/movies/1/

Wähle "PATCH" aus dem Dropdown

{
  "rating": "9.0"
}

Klicke auf "PATCH"

✅ Rating wurde aktualisiert

💻 API mit PowerShell / curl testen

📝 Film erstellen (POST)

# PowerShell:
$body = @{
    title = "Inception"
    year = 2010
    genre = "Sci-Fi"
    rating = "8.8"
    description = "A thief who steals corporate secrets..."
} | ConvertTo-Json

Invoke-RestMethod -Uri "http://127.0.0.1:8000/api/movies/" `
    -Method POST `
    -Body $body `
    -ContentType "application/json"

# Output:
id          : 2
title       : Inception
year        : 2010
genre       : Sci-Fi
rating      : 8.8
age         : 14
description : A thief who steals...

📋 Alle Filme abrufen (GET)

# PowerShell:
Invoke-RestMethod -Uri "http://127.0.0.1:8000/api/movies/"

# Ausgabe: Array von Filmen

# Mit Filtering:
Invoke-RestMethod -Uri "http://127.0.0.1:8000/api/movies/?year=2010"

# Mit Search:
Invoke-RestMethod -Uri "http://127.0.0.1:8000/api/movies/?search=Matrix"

👁️ Einen Film abrufen (GET)

# PowerShell:
Invoke-RestMethod -Uri "http://127.0.0.1:8000/api/movies/1/"

# curl (Linux/Mac):
curl http://127.0.0.1:8000/api/movies/1/

✏️ Film aktualisieren (PATCH)

# PowerShell:
$updateBody = @{
    rating = "9.5"
} | ConvertTo-Json

Invoke-RestMethod -Uri "http://127.0.0.1:8000/api/movies/1/" `
    -Method PATCH `
    -Body $updateBody `
    -ContentType "application/json"

🗑️ Film löschen (DELETE)

# PowerShell:
Invoke-RestMethod -Uri "http://127.0.0.1:8000/api/movies/1/" `
    -Method DELETE

# curl:
curl -X DELETE http://127.0.0.1:8000/api/movies/1/

🎬 Custom Actions testen

# Top-Rated Filme:
Invoke-RestMethod -Uri "http://127.0.0.1:8000/api/movies/top_rated/"

# Besetzung eines Films:
Invoke-RestMethod -Uri "http://127.0.0.1:8000/api/movies/1/castings/"

# Filme eines Künstlers:
Invoke-RestMethod -Uri "http://127.0.0.1:8000/api/artists/1/movies/"

📁 Komplette Dateien - Übersicht

Alle erstellten/modifizierten Dateien

📂 Projektstruktur (Final)

FirstMovieAPI/
├── firstmovieapi/
│   ├── settings.py      ✏️ MODIFIZIERT
│   └── urls.py          ✏️ MODIFIZIERT
├── movies/
│   ├── models.py        ✅ VORHANDEN
│   ├── serializers.py   ✨ NEU
│   ├── views.py         ✨ NEU
│   ├── urls.py          ✨ NEU
│   ├── filters.py       ✨ NEU (optional)
│   └── admin.py         ✏️ MODIFIZIERT (optional)
└── manage.py

📋 Dateien in den nächsten Slides:

  1. movies/models.py (bereits vorhanden)
  2. movies/serializers.py (NEU)
  3. movies/views.py (NEU)
  4. movies/urls.py (NEU)
  5. firstmovieapi/settings.py (modifiziert)
  6. firstmovieapi/urls.py (modifiziert)

📁 movies/models.py (Vollständig)

# filepath: movies/models.py
from django.db import models

class Movie(models.Model):
    """Model für Filme"""
    title = models.CharField(max_length=200, verbose_name="Titel")
    year = models.IntegerField(verbose_name="Erscheinungsjahr")
    genre = models.CharField(max_length=100, verbose_name="Genre", blank=True)
    rating = models.DecimalField(
        max_digits=3, 
        decimal_places=1, 
        null=True, 
        blank=True, 
        verbose_name="Bewertung"
    )
    description = models.TextField(verbose_name="Beschreibung", blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    
    class Meta:
        verbose_name = "Film"
        verbose_name_plural = "Filme"
        ordering = ['-year', 'title']
    
    def __str__(self):
        return f"{self.title} ({self.year})"


class Artist(models.Model):
    """Model für Schauspieler/Künstler"""
    first_name = models.CharField(max_length=100, verbose_name="Vorname")
    last_name = models.CharField(max_length=100, verbose_name="Nachname")
    birth_date = models.DateField(verbose_name="Geburtsdatum", null=True, blank=True)
    nationality = models.CharField(max_length=100, verbose_name="Nationalität", blank=True)
    biography = models.TextField(verbose_name="Biografie", blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    
    class Meta:
        verbose_name = "Künstler"
        verbose_name_plural = "Künstler"
        ordering = ['last_name', 'first_name']
    
    def __str__(self):
        return f"{self.first_name} {self.last_name}"
    
    @property
    def full_name(self):
        return f"{self.first_name} {self.last_name}"


class MovieCasting(models.Model):
    """Brückentabelle: Welcher Artist spielt in welchem Movie"""
    movie = models.ForeignKey(
        Movie, 
        on_delete=models.CASCADE, 
        related_name='castings', 
        verbose_name="Film"
    )
    artist = models.ForeignKey(
        Artist, 
        on_delete=models.CASCADE, 
        related_name='movie_roles', 
        verbose_name="Künstler"
    )
    role_name = models.CharField(max_length=200, verbose_name="Rollenname")
    is_main_role = models.BooleanField(default=False, verbose_name="Hauptrolle")
    order = models.IntegerField(default=0, verbose_name="Reihenfolge")
    created_at = models.DateTimeField(auto_now_add=True)
    
    class Meta:
        verbose_name = "Besetzung"
        verbose_name_plural = "Besetzungen"
        ordering = ['order', 'artist__last_name']
        unique_together = [['movie', 'artist', 'role_name']]
    
    def __str__(self):
        role_type = "Hauptrolle" if self.is_main_role else "Nebenrolle"
        return f"{self.artist.full_name} als {self.role_name} in {self.movie.title} ({role_type})"

🎯 Zusammenfassung

Was haben wir gelernt?

📜 Geschichte & Theorie

  • ✅ DRF Entwicklungsgeschichte (2011-2024)
  • ✅ Warum DRF existiert (Probleme vorher)
  • ✅ Serialisierungsverfahren (JSON, XML, etc.)
  • ✅ CRUD-Operationen verstanden
  • ✅ REST URL-Design Best Practices

📦 Ökosystem & Tools

  • ✅ 10+ Zusatz-Packages kennengelernt
  • ✅ django-filter für komplexe Queries
  • ✅ drf-spectacular für API-Doku
  • ✅ JWT Authentication
  • ✅ CORS für Frontend-Integration

🛠️ Praktische Implementierung

  • ✅ Serializers mit Validierung
  • ✅ ViewSets mit Custom Actions
  • ✅ Router für automatische URLs
  • ✅ Filtering, Search, Ordering
  • ✅ Pagination konfiguriert

🧪 Testing & Tools

  • ✅ Browsable API genutzt
  • ✅ PowerShell/curl Commands
  • ✅ Custom Actions getestet
  • ✅ Filter & Search ausprobiert

❌ Vorher (ohne DRF)

~500+ Zeilen Code

  • Manuelle Serialisierung
  • Eigene Validierung
  • Keine Standards
  • Viel Boilerplate

✅ Nachher (mit DRF)

~150 Zeilen Code

  • Automatische Serialisierung
  • Eingebaute Validierung
  • REST Best Practices
  • Browsable API gratis!

💡 Best Practices & Tipps

1. Serializers

  • ✅ Immer Validierung implementieren
  • ✅ Read-only fields für Timestamps
  • ✅ Separate Serializer für Detail/List
  • ✅ Verschachtelte Serializer sparsam nutzen
  • ❌ Nicht alle Felder exposieren (Sicherheit!)

2. ViewSets

  • ✅ ModelViewSet für einfache CRUD
  • ✅ Custom Actions für Business-Logik
  • ✅ Permissions pro ViewSet/Action
  • ✅ select_related() / prefetch_related() nutzen
  • ❌ Nicht zu viel Logik in Views

3. Filtering

  • ✅ django-filter für komplexe Queries
  • ✅ Search nur für notwendige Felder
  • ✅ Ordering auf indexierte Felder
  • ✅ Custom FilterSet für spezielle Logik
  • ❌ Nicht zu viele filterset_fields

4. Performance

  • ✅ Pagination immer aktivieren
  • ✅ select_related() für ForeignKeys
  • ✅ prefetch_related() für Many-to-Many
  • ✅ Caching für oft abgerufene Daten
  • ✅ Database Indexes auf Filter-Felder

5. Sicherheit

  • ✅ Authentication in Produktion
  • ✅ Permissions granular setzen
  • ✅ Throttling aktivieren
  • ✅ HTTPS in Produktion
  • ❌ Nicht AllowAny in Produktion!

6. Dokumentation

  • ✅ drf-spectacular nutzen
  • ✅ Docstrings in ViewSets
  • ✅ help_text in Serializer-Feldern
  • ✅ Beispiele in Swagger-Doku

🚀 Nächste Schritte - Deine API erweitern

1

Authentication

JWT Tokens

pip install djangorestframework-simplejwt

# Token-basierte Auth
POST /api/token/
→ access & refresh tokens
2

Permissions

Granulare Zugriffsrechte

from rest_framework.permissions import IsAuthenticated

class MovieViewSet(viewsets.ModelViewSet):
    permission_classes = [IsAuthenticated]
3

API Dokumentation

Swagger/OpenAPI

pip install drf-spectacular

# Automatische Swagger-UI
GET /api/docs/
4

Testing

API Tests schreiben

from rest_framework.test import APITestCase

class MovieAPITest(APITestCase):
    def test_create_movie(self):
        # ...
5

Deployment

Production-Ready

  • HTTPS konfigurieren
  • CORS richtig setzen
  • Environment Variables
  • Docker Container

🎉 Gratulation!

Du hast eine vollständige REST API mit Django REST Framework erstellt!

✅ Was du jetzt kannst:

  • 📦 DRF installieren & konfigurieren
  • 🔄 Serializers mit Validierung erstellen
  • 🎯 ViewSets mit Custom Actions
  • 🔗 Automatische URL-Generierung mit Router
  • 🔍 Filtering, Search & Ordering
  • 📄 Pagination implementieren
  • 🧪 API testen (Browser & Terminal)
  • 💡 REST Best Practices anwenden

🎯 Dein nächstes Projekt:

Baue deine eigene API für:

  • 📚 Blog-System
  • 🛒 E-Commerce Shop
  • 📱 Social Media App
  • 📊 Dashboard Backend
  • 🎮 Gaming Platform

📚 Weiterführende Ressourcen:

  • DRF Dokumentation: https://www.django-rest-framework.org/
  • Django Docs: https://docs.djangoproject.com/
  • REST API Design: https://restfulapi.net/
  • HTTP Status Codes: https://httpstatuses.com/

Viel Erfolg mit deinen APIs! 🚀

1 / 30